﻿using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Red.Controls.Shell;
using Red.Controls.TimeLine.Core;
using Red.Controls.TimeLine.Dialogs;
using Red.Controls.TimeLine.Utils;
using Red.Controls.TimeLine.ViewModels;
using Red.Controls.ViewModels;
using Red.Core.Extensions;

namespace Red.Controls.TimeLine
{
    public partial class TimeLine : UserControl, ITimeLineBackendObserver, INotifyPropertyChanged
    {
        #region Public Data

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region Data

        private readonly TimeLineBackendProxy _backend = null;
        private readonly TimeLineControler _controler = new TimeLineControler();

        private TracksRoot _tracksRoot = null;
        private SectorsRoot _sectorsRoot = null;

        private ScrollViewer _backgroundTreeScrollViewer = null;
        private ScrollViewer _tracksTreeScrollViewer = null;
        private ScrollViewer _trackHeadersTreeScrollViewer = null;

        private Point _addNewEventPosition = new Point();

        private TrackItemBase _selectedTrackItem = null;

        #endregion

        #region C-tor

        public TimeLine()
        {
            // Calls it before IntializeComponent due to Tracks are binded to child controls of this control
            _backend = new TimeLineBackendProxy( this );
            _tracksRoot = new TracksRoot( _backend, _controler );
            _sectorsRoot = new SectorsRoot( _backend, _controler );

            InitializeComponent();
           
            DataContextChanged += OnDataContextChanged;
            Loaded += TimeLine_Loaded;
            
            _controler.StateChanged += OnTimeLineControlerStateChanged;
            _tracksTree.MouseMove += OnTracksTreeMouseMove;
            _tracksTree.SelectedItemChanged += OnTracksTreeSelectedItemChanged;

            _tracksTree.TimeTrackControler = _controler;
            _timeRulerTop.TimeLineControler = _controler;
            _timeRulerDown.TimeLineControler = _controler;
            _cursorsPanel.TimeLineControler = _controler;
        }

        #endregion

        #region ITimeLineBackendObserver implementation

        public async Task OnTrackCreatedAsync( TimeLineId parentId, TimeLineId trackId )
        {
            await _tracksRoot.OnTrackCreatedAsync( parentId, trackId );
        }

        public async Task OnTrackDeletedAsync( TimeLineId parentId, TimeLineId trackId )
        {
            await _tracksRoot.OnTrackRemovedAsync( parentId, trackId );
        }

        public async Task OnEventCreatedAsync( TimeLineId trackId, TimeLineId eventId )
        {
            await _tracksRoot.OnEventCreatedAsync( trackId, eventId );
        }

        public async Task OnEventDeletedAsync( TimeLineId trackId, TimeLineId eventId )
        {
            await _tracksRoot.OnEventRemovedAsync( trackId, eventId );
        }

        public async Task OnSectorCreatedAsync( TimeLineId sectorId )
        {
            await _sectorsRoot.OnSectorCreatedAsync( sectorId );
        }

        public async void OnSectorDeletedAsync( TimeLineId sectorId )
        {
            await _sectorsRoot.OnSectorRemovedAsync( sectorId );
        }

        #endregion

        #region Properties

        public SectorsRoot Sectors
        {
            get { return _sectorsRoot; }
        }

        public TracksRoot Tracks
        {
            get { return _tracksRoot; }
        }

        public TimeLineControler TimeLineControler
        {
            get { return _controler; }
        }

        public TrackItemBase SelectedTrackItem
        {
            get { return _selectedTrackItem; }
            set
            {
                if ( _selectedTrackItem != value )
                {
                    _selectedTrackItem = value;
                    OnPropertyChanged();
                }
            }
        }

        #endregion

        #region Dependency properties

        public double MimimumTime
        {
            get { return (double)GetValue( MimimumTimeProperty ); }
            set { SetValue( MimimumTimeProperty, value ); }
        }

        public double MaximumTime
        {
            get { return (double)GetValue( MaximumTimeProperty ); }
            set { SetValue( MaximumTimeProperty, value ); }
        }

        public static readonly DependencyProperty MimimumTimeProperty =
            DependencyProperty.Register( "MimimumTime", typeof( double ), typeof( TimeLine ),
                new PropertyMetadata( 0.0 ) );

        public static readonly DependencyProperty MaximumTimeProperty =
            DependencyProperty.Register( "MaximumTime", typeof( double ), typeof( TimeLine ),
                new PropertyMetadata( 120.0 ) );

        #endregion

        #region Private event handlers

        private async void OnDataContextChanged( object sender, DependencyPropertyChangedEventArgs e )
        {
            if ( e.NewValue is DataItem )
            {
                InteropDataItem timeline = e.NewValue as InteropDataItem;
                await _backend.InitializeAsync( timeline );
           
                _tracksRoot.Rebuild();
                _sectorsRoot.Rebuild();
            }
        }

        private void OnTimeLineControlerStateChanged( object sender, EventArgs e )
        {
            _tracksRoot.RecalculateVisualsProperties();
            _sectorsRoot.RecalculateVisualsProperties();
        }

        private void OnTracksTreeMouseMove( object sender, MouseEventArgs e )
        {
            if ( _selectionCursor != null )
            {
                double offset = Mouse.GetPosition( _cursorsPanel ).X;
                double time = TimeLineControler.ScreenOffsetToTime( offset );

                if ( time < TimeLineControler.MinTimeLineValue )
                {
                    time = TimeLineControler.MinTimeLineValue;
                }

                if ( time > TimeLineControler.MaxTimeLineValue )
                {
                    time = TimeLineControler.MaxTimeLineValue;
                }
                _selectionCursor.Time = time;
            }
        }

        private void OnTracksTreeSelectedItemChanged( object sender, RoutedPropertyChangedEventArgs<object> e )
        {
            object selectedObject = e.NewValue;
            if ( selectedObject != null && selectedObject is TrackItemBase )
            {
                TrackItemBase item = selectedObject as TrackItemBase;
                _backend.ExecuteSelectedItem( item.Id );
            }         
        }

        #endregion

        #region Trees synchronization code

        private void TimeLine_Loaded( object sender, System.Windows.RoutedEventArgs e )
        {
            // Tracks and headers trees have to synchronize ScrollView 
            _tracksTreeScrollViewer = VisualTreeUtils.GetChild<ScrollViewer>( _tracksTree );
            _trackHeadersTreeScrollViewer = VisualTreeUtils.GetChild<ScrollViewer>( _trackHeadersTree );
            _backgroundTreeScrollViewer = VisualTreeUtils.GetChild<ScrollViewer>( _backgroundTree );

            _tracksTreeScrollViewer.ScrollChanged += OnScrollViewerScrollChange;
            _trackHeadersTreeScrollViewer.ScrollChanged += OnScrollViewerScrollChange;
        
            _tracksTreeScrollViewer.ContextMenuOpening += OnTracksTreeScrollViewer_ContextMenuOpening;
        }

        private void OnTracksTreeScrollViewer_ContextMenuOpening( object sender, ContextMenuEventArgs e )
        {
            _addNewEventPosition = new Point( e.CursorLeft, e.CursorTop );
        }       

        private void OnScrollViewerScrollChange( object sender, ScrollChangedEventArgs e )
        {
            _tracksTreeScrollViewer.ScrollToVerticalOffset( _trackHeadersTreeScrollViewer.VerticalOffset );
            _backgroundTreeScrollViewer.ScrollToVerticalOffset( _trackHeadersTreeScrollViewer.VerticalOffset );
        }

        #endregion

        #region Public Methods

        public void AddTrack( TrackBase parentTrack, string type )
        {
            if ( parentTrack != null )
            {
                _backend.ExecuteAddTrack( parentTrack.Id, type );
            }
        }

        public void RemoveTrack( TrackBase track )
        {
            if ( track != null )
            {
                _backend.ExecuteRemoveTrack( track.Id );
            }
        }

        public void RenameTrack( TrackBase track, string name )
        {
            if ( track != null )
            {
                track.Name = name;
            }
        }

        public async Task AddEventAsync( TrackBase track, string type, double time, double duration )
        {
            TimeLineId id = await _backend.ExecuteAddEventAsync( track.Id, type );

            TrackItemBase item = ( track as Track ).GetItemById( id );
            if ( item != null )
            {
                item.ActionTime = time;

                if ( item is DurationTrackEvent )
                {
                    ( item as DurationTrackEvent ).DurationTime = duration;
                }
            }          
        }

        public void RemoveEvent( TrackItemBase item )
        {
            if ( item != null )
            {
                _backend.ExecuteRemoveEvent( item.Id );
            }
        }

        #endregion

        #region UI private methods

        private TrackBase FindClickedTrack( Point clickPosition )
        {
            // Hack: We need to find out which object was hit by mouse click

            HitTestResult result = VisualTreeHelper.HitTest( _backgroundTree, clickPosition );
            if ( result != null )
            {
                DependencyObject hitResult = result.VisualHit;

                // Hack: TrackContainer object contain only a Border object (See TrackContainerStyle 
                // definition) and we are looking for the Border to find a track object...
                if ( ( hitResult != null ) && ( hitResult is Border ) )
                {
                    Border border = (Border)hitResult;
                    if ( border.DataContext is Track )
                    {
                        return border.DataContext as TrackBase;
                    }
                }
            }

            return null;
        }

        #endregion

        #region Protected Methods

        protected virtual void OnPropertyChanged( [CallerMemberName] string propertyName = null )
        {
            if ( PropertyChanged != null )
            {
                PropertyChanged( this, new PropertyChangedEventArgs( propertyName ) );
            }
        }

        #endregion

        #region Command Binding

        private void AddTrack_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            // Check if source of event was a track or timeline toolbar button
            TrackBase parentTrace = e.Parameter is TrackBase ? e.Parameter as TrackBase : _tracksRoot;
            AddTrack( parentTrace, "Track" );
        }

        private void AddTracksGroup_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            // Check if source of event was a track or timeline toolbar button
            TrackBase parentTrace = e.Parameter is TrackBase ? e.Parameter as TrackBase : _tracksRoot;
            AddTrack( parentTrace, "TracksGroup" );
        }

        private async void AddTrackDialog_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            AddTrackDialog addTrackDialog = new AddTrackDialog( Window.GetWindow( this ) );

            if ( addTrackDialog.Show() )
            {
                TimeLineId newTrackId = await _backend.ExecuteAddTrackAsync( _tracksRoot.Id, addTrackDialog.TrackType );

                if ( newTrackId != TimeLineId.InvalidId )
                {
                    TrackBase track = _tracksRoot.GetTrackById( newTrackId );
                    track.Name = addTrackDialog.TrackName;
                }
            }
        }

        private void RenameTrack_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            TrackBase track = null;
            if ( e.Parameter is TrackBase )
            {
                track = e.Parameter as TrackBase;

                var newTrackName = TextInputDialog.Show( this, track.Name, "Name:", "Rename track" );
                if ( newTrackName == null || newTrackName.Length == 0 )
                {
                    return;
                }

                RenameTrack( track, newTrackName );
            }
        }

        private void DeleteTrack_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            TrackBase track = null;

            // Check if track object was a source of event
            if ( e.Parameter is TrackBase )
            {
                track = e.Parameter as TrackBase;
            }
            else
            {
                // Toolbar button was clicked so find selected treeview node
                if ( ( _trackHeadersTree.SelectedItem != null ) && ( _trackHeadersTree.SelectedItem is TrackBase ) )
                {
                    track = _trackHeadersTree.SelectedItem as TrackBase;                   
                }                    
            }

            RemoveTrack( track );
        }

        private void GroupTracks_Executed( object sender, ExecutedRoutedEventArgs e )
        {
        }

        private void UngroupTrack_Executed( object sender, ExecutedRoutedEventArgs e )
        {
        }

        private void AddGroup_Executed( object sender, ExecutedRoutedEventArgs e )
        {
        }

        private void DeleteGroup_Executed( object sender, ExecutedRoutedEventArgs e )
        {
        }

        private void CreateSector_Executed( object sender, ExecutedRoutedEventArgs e )
        {
        }

        private void DeleteSector_Executed( object sender, ExecutedRoutedEventArgs e )
        {      
        }

        private void ActivateSector_Executed( object sender, ExecutedRoutedEventArgs e )
        {
        }

        private void DeactivateSector_Executed( object sender, ExecutedRoutedEventArgs e )
        {
        }

        private void AddEvent_Executed( object sender, ExecutedRoutedEventArgs e )
        {
        }

        private void DeleteEvent_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            TrackItemBase item = null;

            // Check if track object was a source of event
            if ( e.Parameter is TrackItemBase )
            {
                item = e.Parameter as TrackItemBase;
            }

            RemoveEvent( item );
        }        

        private void AddPointEvent_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            TrackBase track = FindClickedTrack( _addNewEventPosition );
            Double time = _controler.ScreenOffsetToTime( _addNewEventPosition.X );

            AddEventAsync( track, "PointEvent", time, 0 ).DoNotAwait();
        }

        private void AddDurationEvent_Executed( object sender, ExecutedRoutedEventArgs e )
        {
            TrackBase track = FindClickedTrack( _addNewEventPosition );
            Double time = _controler.ScreenOffsetToTime( _addNewEventPosition.X );

            AddEventAsync( track, "DurationEvent", time, 10 ).DoNotAwait();
        }

        #endregion
    }
}
